﻿/*************************************************************************************

   Extended WPF Toolkit

   Copyright (C) 2007-2013 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license 

   For more features, controls, and fast professional support,
   pick up the Plus Edition at http://xceed.com/wpf_toolkit

   Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids

  ***********************************************************************************/

/**************************************************************************\
    Copyright Microsoft Corporation. All Rights Reserved.
\**************************************************************************/

// This file contains general utilities to aid in development.
// Classes here generally shouldn't be exposed publicly since
// they're not particular to any library functionality.
// Because the classes here are internal, it's likely this file
// might be included in multiple assemblies.
namespace Standard
{
  using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Diagnostics.CodeAnalysis;
  using System.Globalization;
  using System.IO;
  using System.Reflection;
  using System.Runtime.InteropServices;
  using System.Security.Cryptography;
  using System.Text;
  using System.Windows;
  using System.Windows.Media;
  using System.Windows.Media.Imaging;

  internal static partial class Utility
  {
    private static readonly Version _osVersion = Environment.OSVersion.Version;
    private static readonly Version _presentationFrameworkVersion = Assembly.GetAssembly( typeof( Window ) ).GetName().Version;

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    private static bool _MemCmp( IntPtr left, IntPtr right, long cb )
    {
      int offset = 0;

      for( ; offset < ( cb - sizeof( Int64 ) ); offset += sizeof( Int64 ) )
      {
        Int64 left64 = Marshal.ReadInt64( left, offset );
        Int64 right64 = Marshal.ReadInt64( right, offset );

        if( left64 != right64 )
        {
          return false;
        }
      }

      for( ; offset < cb; offset += sizeof( byte ) )
      {
        byte left8 = Marshal.ReadByte( left, offset );
        byte right8 = Marshal.ReadByte( right, offset );

        if( left8 != right8 )
        {
          return false;
        }
      }

      return true;
    }

    /// <summary>The native RGB macro.</summary>
    /// <param name="c"></param>
    /// <returns></returns>
    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static int RGB( Color c )
    {
      return c.R | ( c.G << 8 ) | ( c.B << 16 );
    }

    /// <summary>Convert a native integer that represent a color with an alpha channel into a Color struct.</summary>
    /// <param name="color">The integer that represents the color.  Its bits are of the format 0xAARRGGBB.</param>
    /// <returns>A Color representation of the parameter.</returns>
    public static Color ColorFromArgbDword( uint color )
    {
      return Color.FromArgb(
          ( byte )( ( color & 0xFF000000 ) >> 24 ),
          ( byte )( ( color & 0x00FF0000 ) >> 16 ),
          ( byte )( ( color & 0x0000FF00 ) >> 8 ),
          ( byte )( ( color & 0x000000FF ) >> 0 ) );
    }


    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static int GET_X_LPARAM( IntPtr lParam )
    {
      return LOWORD( lParam.ToInt32() );
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static int GET_Y_LPARAM( IntPtr lParam )
    {
      return HIWORD( lParam.ToInt32() );
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static int HIWORD( int i )
    {
      return ( short )( i >> 16 );
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static int LOWORD( int i )
    {
      return ( short )( i & 0xFFFF );
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    [SuppressMessage( "Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands" )]
    public static bool AreStreamsEqual( Stream left, Stream right )
    {
      if( null == left )
      {
        return right == null;
      }
      if( null == right )
      {
        return false;
      }

      if( !left.CanRead || !right.CanRead )
      {
        throw new NotSupportedException( "The streams can't be read for comparison" );
      }

      if( left.Length != right.Length )
      {
        return false;
      }

      var length = ( int )left.Length;

      // seek to beginning
      left.Position = 0;
      right.Position = 0;

      // total bytes read
      int totalReadLeft = 0;
      int totalReadRight = 0;

      // bytes read on this iteration
      int cbReadLeft = 0;
      int cbReadRight = 0;

      // where to store the read data
      var leftBuffer = new byte[ 512 ];
      var rightBuffer = new byte[ 512 ];

      // pin the left buffer
      GCHandle handleLeft = GCHandle.Alloc( leftBuffer, GCHandleType.Pinned );
      IntPtr ptrLeft = handleLeft.AddrOfPinnedObject();

      // pin the right buffer
      GCHandle handleRight = GCHandle.Alloc( rightBuffer, GCHandleType.Pinned );
      IntPtr ptrRight = handleRight.AddrOfPinnedObject();

      try
      {
        while( totalReadLeft < length )
        {
          Assert.AreEqual( totalReadLeft, totalReadRight );

          cbReadLeft = left.Read( leftBuffer, 0, leftBuffer.Length );
          cbReadRight = right.Read( rightBuffer, 0, rightBuffer.Length );

          // verify the contents are an exact match
          if( cbReadLeft != cbReadRight )
          {
            return false;
          }

          if( !_MemCmp( ptrLeft, ptrRight, cbReadLeft ) )
          {
            return false;
          }

          totalReadLeft += cbReadLeft;
          totalReadRight += cbReadRight;
        }

        Assert.AreEqual( cbReadLeft, cbReadRight );
        Assert.AreEqual( totalReadLeft, totalReadRight );
        Assert.AreEqual( length, totalReadLeft );

        return true;
      }
      finally
      {
        handleLeft.Free();
        handleRight.Free();
      }
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static bool GuidTryParse( string guidString, out Guid guid )
    {
      Verify.IsNeitherNullNorEmpty( guidString, "guidString" );

      try
      {
        guid = new Guid( guidString );
        return true;
      }
      catch( FormatException )
      {
      }
      catch( OverflowException )
      {
      }
      // Doesn't seem to be a valid guid.
      guid = default( Guid );
      return false;
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static bool IsFlagSet( int value, int mask )
    {
      return 0 != ( value & mask );
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static bool IsFlagSet( uint value, uint mask )
    {
      return 0 != ( value & mask );
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static bool IsFlagSet( long value, long mask )
    {
      return 0 != ( value & mask );
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static bool IsFlagSet( ulong value, ulong mask )
    {
      return 0 != ( value & mask );
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static bool IsOSVistaOrNewer
    {
      get
      {
        return _osVersion >= new Version( 6, 0 );
      }
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static bool IsOSWindows7OrNewer
    {
      get
      {
        return _osVersion >= new Version( 6, 1 );
      }
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static bool IsOSWindows8OrNewer
    {
      get
      {
        return _osVersion >= new Version( 6, 2 );
      }
    }

    /// <summary>
    /// Is this using WPF4?
    /// </summary>
    /// <remarks>
    /// There are a few specific bugs in Window in 3.5SP1 and below that require workarounds
    /// when handling WM_NCCALCSIZE on the HWND.
    /// </remarks>
    public static bool IsPresentationFrameworkVersionLessThan4
    {
      get
      {
        return _presentationFrameworkVersion < new Version( 4, 0 );
      }
    }

    // Caller is responsible for destroying the HICON
    // Caller is responsible to ensure that GDI+ has been initialized.
    [SuppressMessage( "Microsoft.Usage", "CA2202:Do not dispose objects multiple times" )]
    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static IntPtr GenerateHICON( ImageSource image, Size dimensions )
    {
      if( image == null )
      {
        return IntPtr.Zero;
      }

      // If we're getting this from a ".ico" resource, then it comes through as a BitmapFrame.
      // We can use leverage this as a shortcut to get the right 16x16 representation
      // because DrawImage doesn't do that for us.
      var bf = image as BitmapFrame;
      if( bf != null )
      {
        bf = GetBestMatch( bf.Decoder.Frames, ( int )dimensions.Width, ( int )dimensions.Height );
      }
      else
      {
        // Constrain the dimensions based on the aspect ratio.
        var drawingDimensions = new Rect( 0, 0, dimensions.Width, dimensions.Height );

        // There's no reason to assume that the requested image dimensions are square.
        double renderRatio = dimensions.Width / dimensions.Height;
        double aspectRatio = image.Width / image.Height;

        // If it's smaller than the requested size, then place it in the middle and pad the image.
        if( image.Width <= dimensions.Width && image.Height <= dimensions.Height )
        {
          drawingDimensions = new Rect( ( dimensions.Width - image.Width ) / 2, ( dimensions.Height - image.Height ) / 2, image.Width, image.Height );
        }
        else if( renderRatio > aspectRatio )
        {
          double scaledRenderWidth = ( image.Width / image.Height ) * dimensions.Width;
          drawingDimensions = new Rect( ( dimensions.Width - scaledRenderWidth ) / 2, 0, scaledRenderWidth, dimensions.Height );
        }
        else if( renderRatio < aspectRatio )
        {
          double scaledRenderHeight = ( image.Height / image.Width ) * dimensions.Height;
          drawingDimensions = new Rect( 0, ( dimensions.Height - scaledRenderHeight ) / 2, dimensions.Width, scaledRenderHeight );
        }

        var dv = new DrawingVisual();
        DrawingContext dc = dv.RenderOpen();
        dc.DrawImage( image, drawingDimensions );
        dc.Close();

        var bmp = new RenderTargetBitmap( ( int )dimensions.Width, ( int )dimensions.Height, 96, 96, PixelFormats.Pbgra32 );
        bmp.Render( dv );
        bf = BitmapFrame.Create( bmp );
      }

      // Using GDI+ to convert to an HICON.
      // I'd rather not duplicate their code.
      using( MemoryStream memstm = new MemoryStream() )
      {
        BitmapEncoder enc = new PngBitmapEncoder();
        enc.Frames.Add( bf );
        enc.Save( memstm );

        using( var istm = new ManagedIStream( memstm ) )
        {
          // We are not bubbling out GDI+ errors when creating the native image fails.
          IntPtr bitmap = IntPtr.Zero;
          try
          {
            Status gpStatus = NativeMethods.GdipCreateBitmapFromStream( istm, out bitmap );
            if( Status.Ok != gpStatus )
            {
              return IntPtr.Zero;
            }

            IntPtr hicon;
            gpStatus = NativeMethods.GdipCreateHICONFromBitmap( bitmap, out hicon );
            if( Status.Ok != gpStatus )
            {
              return IntPtr.Zero;
            }

            // Caller is responsible for freeing this.
            return hicon;
          }
          finally
          {
            Utility.SafeDisposeImage( ref bitmap );
          }
        }
      }
    }

    public static BitmapFrame GetBestMatch( IList<BitmapFrame> frames, int width, int height )
    {
      return _GetBestMatch( frames, _GetBitDepth(), width, height );
    }

    private static int _MatchImage( BitmapFrame frame, int bitDepth, int width, int height, int bpp )
    {
      int score = 2 * _WeightedAbs( bpp, bitDepth, false ) +
              _WeightedAbs( frame.PixelWidth, width, true ) +
              _WeightedAbs( frame.PixelHeight, height, true );

      return score;
    }

    private static int _WeightedAbs( int valueHave, int valueWant, bool fPunish )
    {
      int diff = ( valueHave - valueWant );

      if( diff < 0 )
      {
        diff = ( fPunish ? -2 : -1 ) * diff;
      }

      return diff;
    }

    /// From a list of BitmapFrames find the one that best matches the requested dimensions.
    /// The methods used here are copied from Win32 sources.  We want to be consistent with
    /// system behaviors.
    private static BitmapFrame _GetBestMatch( IList<BitmapFrame> frames, int bitDepth, int width, int height )
    {
      int bestScore = int.MaxValue;
      int bestBpp = 0;
      int bestIndex = 0;

      bool isBitmapIconDecoder = frames[ 0 ].Decoder is IconBitmapDecoder;

      for( int i = 0; i < frames.Count && bestScore != 0; ++i )
      {
        int currentIconBitDepth = isBitmapIconDecoder ? frames[ i ].Thumbnail.Format.BitsPerPixel : frames[ i ].Format.BitsPerPixel;

        if( currentIconBitDepth == 0 )
        {
          currentIconBitDepth = 8;
        }

        int score = _MatchImage( frames[ i ], bitDepth, width, height, currentIconBitDepth );
        if( score < bestScore )
        {
          bestIndex = i;
          bestBpp = currentIconBitDepth;
          bestScore = score;
        }
        else if( score == bestScore )
        {
          // Tie breaker: choose the higher color depth.  If that fails, choose first one.
          if( bestBpp < currentIconBitDepth )
          {
            bestIndex = i;
            bestBpp = currentIconBitDepth;
          }
        }
      }

      return frames[ bestIndex ];
    }

    // This can be cached.  It's not going to change under reasonable circumstances.
    private static int s_bitDepth; // = 0;
    private static int _GetBitDepth()
    {
      if( s_bitDepth == 0 )
      {
        using( SafeDC dc = SafeDC.GetDesktop() )
        {
          s_bitDepth = NativeMethods.GetDeviceCaps( dc, DeviceCap.BITSPIXEL ) * NativeMethods.GetDeviceCaps( dc, DeviceCap.PLANES );
        }
      }
      return s_bitDepth;
    }

    /// <summary>
    /// Simple guard against the exceptions that File.Delete throws on null and empty strings.
    /// </summary>
    /// <param name="path">The path to delete.  Unlike File.Delete, this can be null or empty.</param>
    /// <remarks>
    /// Note that File.Delete, and by extension SafeDeleteFile, does not throw an exception
    /// if the file does not exist.
    /// </remarks>
    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static void SafeDeleteFile( string path )
    {
      if( !string.IsNullOrEmpty( path ) )
      {

        File.Delete( path );
      }
    }

    /// <summary>GDI's DeleteObject</summary>
    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static void SafeDeleteObject( ref IntPtr gdiObject )
    {
      IntPtr p = gdiObject;
      gdiObject = IntPtr.Zero;
      if( IntPtr.Zero != p )
      {
        NativeMethods.DeleteObject( p );
      }
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static void SafeDestroyIcon( ref IntPtr hicon )
    {
      IntPtr p = hicon;
      hicon = IntPtr.Zero;
      if( IntPtr.Zero != p )
      {
        NativeMethods.DestroyIcon( p );
      }
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static void SafeDestroyWindow( ref IntPtr hwnd )
    {
      IntPtr p = hwnd;
      hwnd = IntPtr.Zero;
      if( NativeMethods.IsWindow( p ) )
      {
        NativeMethods.DestroyWindow( p );
      }
    }


    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static void SafeDispose<T>( ref T disposable ) where T : IDisposable
    {
      // Dispose can safely be called on an object multiple times.
      IDisposable t = disposable;
      disposable = default( T );
      if( null != t )
      {
        t.Dispose();
      }
    }

    /// <summary>GDI+'s DisposeImage</summary>
    /// <param name="gdipImage"></param>
    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static void SafeDisposeImage( ref IntPtr gdipImage )
    {
      IntPtr p = gdipImage;
      gdipImage = IntPtr.Zero;
      if( IntPtr.Zero != p )
      {
        NativeMethods.GdipDisposeImage( p );
      }
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    [SuppressMessage( "Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands" )]
    public static void SafeCoTaskMemFree( ref IntPtr ptr )
    {
      IntPtr p = ptr;
      ptr = IntPtr.Zero;
      if( IntPtr.Zero != p )
      {
        Marshal.FreeCoTaskMem( p );
      }
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    [SuppressMessage( "Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands" )]
    public static void SafeFreeHGlobal( ref IntPtr hglobal )
    {
      IntPtr p = hglobal;
      hglobal = IntPtr.Zero;
      if( IntPtr.Zero != p )
      {
        Marshal.FreeHGlobal( p );
      }
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    [SuppressMessage( "Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands" )]
    public static void SafeRelease<T>( ref T comObject ) where T : class
    {
      T t = comObject;
      comObject = default( T );
      if( null != t )
      {
        Assert.IsTrue( Marshal.IsComObject( t ) );
        Marshal.ReleaseComObject( t );
      }
    }

    /// <summary>
    /// Utility to help classes catenate their properties for implementing ToString().
    /// </summary>
    /// <param name="source">The StringBuilder to catenate the results into.</param>
    /// <param name="propertyName">The name of the property to be catenated.</param>
    /// <param name="value">The value of the property to be catenated.</param>
    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static void GeneratePropertyString( StringBuilder source, string propertyName, string value )
    {
      Assert.IsNotNull( source );
      Assert.IsFalse( string.IsNullOrEmpty( propertyName ) );

      if( 0 != source.Length )
      {
        source.Append( ' ' );
      }

      source.Append( propertyName );
      source.Append( ": " );
      if( string.IsNullOrEmpty( value ) )
      {
        source.Append( "<null>" );
      }
      else
      {
        source.Append( '\"' );
        source.Append( value );
        source.Append( '\"' );
      }
    }

    /// <summary>
    /// Generates ToString functionality for a struct.  This is an expensive way to do it,
    /// it exists for the sake of debugging while classes are in flux.
    /// Eventually this should just be removed and the classes should
    /// do this without reflection.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="object"></param>
    /// <returns></returns>
    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    [Obsolete]
    public static string GenerateToString<T>( T @object ) where T : struct
    {
      var sbRet = new StringBuilder();
      foreach( PropertyInfo property in typeof( T ).GetProperties( BindingFlags.Public | BindingFlags.Instance ) )
      {
        if( 0 != sbRet.Length )
        {
          sbRet.Append( ", " );
        }
        Assert.AreEqual( 0, property.GetIndexParameters().Length );
        object value = property.GetValue( @object, null );
        string format = null == value ? "{0}: <null>" : "{0}: \"{1}\"";
        sbRet.AppendFormat( format, property.Name, value );
      }
      return sbRet.ToString();
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static void CopyStream( Stream destination, Stream source )
    {
      Assert.IsNotNull( source );
      Assert.IsNotNull( destination );

      destination.Position = 0;

      // If we're copying from, say, a web stream, don't fail because of this.
      if( source.CanSeek )
      {
        source.Position = 0;

        // Consider that this could throw because 
        // the source stream doesn't know it's size...
        destination.SetLength( source.Length );
      }

      var buffer = new byte[ 4096 ];
      int cbRead;

      do
      {
        cbRead = source.Read( buffer, 0, buffer.Length );
        if( 0 != cbRead )
        {
          destination.Write( buffer, 0, cbRead );
        }
      }
      while( buffer.Length == cbRead );

      // Reset the Seek pointer before returning.
      destination.Position = 0;
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static string HashStreamMD5( Stream stm )
    {
      stm.Position = 0;
      var hashBuilder = new StringBuilder();
      using( MD5 md5 = MD5.Create() )
      {
        foreach( byte b in md5.ComputeHash( stm ) )
        {
          hashBuilder.Append( b.ToString( "x2", CultureInfo.InvariantCulture ) );
        }
      }

      return hashBuilder.ToString();
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static void EnsureDirectory( string path )
    {
      if( !Directory.Exists( Path.GetDirectoryName( path ) ) )
      {
        Directory.CreateDirectory( Path.GetDirectoryName( path ) );
      }
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static bool MemCmp( byte[] left, byte[] right, int cb )
    {
      Assert.IsNotNull( left );
      Assert.IsNotNull( right );

      Assert.IsTrue( cb <= Math.Min( left.Length, right.Length ) );

      // pin this buffer
      GCHandle handleLeft = GCHandle.Alloc( left, GCHandleType.Pinned );
      IntPtr ptrLeft = handleLeft.AddrOfPinnedObject();

      // pin the other buffer
      GCHandle handleRight = GCHandle.Alloc( right, GCHandleType.Pinned );
      IntPtr ptrRight = handleRight.AddrOfPinnedObject();

      bool fRet = _MemCmp( ptrLeft, ptrRight, cb );

      handleLeft.Free();
      handleRight.Free();

      return fRet;
    }

    private class _UrlDecoder
    {
      private readonly Encoding _encoding;
      private readonly char[] _charBuffer;
      private readonly byte[] _byteBuffer;
      private int _byteCount;
      private int _charCount;

      [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
      public _UrlDecoder( int size, Encoding encoding )
      {
        _encoding = encoding;
        _charBuffer = new char[ size ];
        _byteBuffer = new byte[ size ];
      }

      [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
      public void AddByte( byte b )
      {
        _byteBuffer[ _byteCount++ ] = b;
      }

      [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
      public void AddChar( char ch )
      {
        _FlushBytes();
        _charBuffer[ _charCount++ ] = ch;
      }

      [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
      private void _FlushBytes()
      {
        if( _byteCount > 0 )
        {
          _charCount += _encoding.GetChars( _byteBuffer, 0, _byteCount, _charBuffer, _charCount );
          _byteCount = 0;
        }
      }

      [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
      public string GetString()
      {
        _FlushBytes();
        if( _charCount > 0 )
        {
          return new string( _charBuffer, 0, _charCount );
        }
        return "";
      }
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static string UrlDecode( string url )
    {
      if( url == null )
      {
        return null;
      }

      var decoder = new _UrlDecoder( url.Length, Encoding.UTF8 );
      int length = url.Length;
      for( int i = 0; i < length; ++i )
      {
        char ch = url[ i ];

        if( ch == '+' )
        {
          decoder.AddByte( ( byte )' ' );
          continue;
        }

        if( ch == '%' && i < length - 2 )
        {
          // decode %uXXXX into a Unicode character.
          if( url[ i + 1 ] == 'u' && i < length - 5 )
          {
            int a = _HexToInt( url[ i + 2 ] );
            int b = _HexToInt( url[ i + 3 ] );
            int c = _HexToInt( url[ i + 4 ] );
            int d = _HexToInt( url[ i + 5 ] );
            if( a >= 0 && b >= 0 && c >= 0 && d >= 0 )
            {
              decoder.AddChar( ( char )( ( a << 12 ) | ( b << 8 ) | ( c << 4 ) | d ) );
              i += 5;

              continue;
            }
          }
          else
          {
            // decode %XX into a Unicode character.
            int a = _HexToInt( url[ i + 1 ] );
            int b = _HexToInt( url[ i + 2 ] );

            if( a >= 0 && b >= 0 )
            {
              decoder.AddByte( ( byte )( ( a << 4 ) | b ) );
              i += 2;

              continue;
            }
          }
        }

        // Add any 7bit character as a byte.
        if( ( ch & 0xFF80 ) == 0 )
        {
          decoder.AddByte( ( byte )ch );
        }
        else
        {
          decoder.AddChar( ch );
        }
      }

      return decoder.GetString();
    }

    /// <summary>
    /// Encodes a URL string.  Duplicated functionality from System.Web.HttpUtility.UrlEncode.
    /// </summary>
    /// <param name="url"></param>
    /// <returns></returns>
    /// <remarks>
    /// Duplicated from System.Web.HttpUtility because System.Web isn't part of the client profile.
    /// URL Encoding replaces ' ' with '+' and unsafe ASCII characters with '%XX'.
    /// Safe characters are defined in RFC2396 (http://www.ietf.org/rfc/rfc2396.txt).
    /// They are the 7-bit ASCII alphanumerics and the mark characters "-_.!~*'()".
    /// This implementation does not treat '~' as a safe character to be consistent with the System.Web version.
    /// </remarks>
    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    public static string UrlEncode( string url )
    {
      if( url == null )
      {
        return null;
      }

      byte[] bytes = Encoding.UTF8.GetBytes( url );

      bool needsEncoding = false;
      int unsafeCharCount = 0;
      foreach( byte b in bytes )
      {
        if( b == ' ' )
        {
          needsEncoding = true;
        }
        else if( !_UrlEncodeIsSafe( b ) )
        {
          ++unsafeCharCount;
          needsEncoding = true;
        }
      }

      if( needsEncoding )
      {
        var buffer = new byte[ bytes.Length + ( unsafeCharCount * 2 ) ];
        int writeIndex = 0;
        foreach( byte b in bytes )
        {
          if( _UrlEncodeIsSafe( b ) )
          {
            buffer[ writeIndex++ ] = b;
          }
          else if( b == ' ' )
          {
            buffer[ writeIndex++ ] = ( byte )'+';
          }
          else
          {
            buffer[ writeIndex++ ] = ( byte )'%';
            buffer[ writeIndex++ ] = _IntToHex( ( b >> 4 ) & 0xF );
            buffer[ writeIndex++ ] = _IntToHex( b & 0xF );
          }
        }
        bytes = buffer;
        Assert.AreEqual( buffer.Length, writeIndex );
      }

      return Encoding.ASCII.GetString( bytes );
    }

    // HttpUtility's UrlEncode is slightly different from the RFC.
    // RFC2396 describes unreserved characters as alphanumeric or
    // the list "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
    // The System.Web version unnecessarily escapes '~', which should be okay...
    // Keeping that same pattern here just to be consistent.
    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    private static bool _UrlEncodeIsSafe( byte b )
    {
      if( _IsAsciiAlphaNumeric( b ) )
      {
        return true;
      }

      switch( ( char )b )
      {
        case '-':
        case '_':
        case '.':
        case '!':
        //case '~':
        case '*':
        case '\'':
        case '(':
        case ')':
          return true;
      }

      return false;
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    private static bool _IsAsciiAlphaNumeric( byte b )
    {
      return ( b >= 'a' && b <= 'z' )
          || ( b >= 'A' && b <= 'Z' )
          || ( b >= '0' && b <= '9' );
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    private static byte _IntToHex( int n )
    {
      Assert.BoundedInteger( 0, n, 16 );
      if( n <= 9 )
      {
        return ( byte )( n + '0' );
      }
      return ( byte )( n - 10 + 'A' );
    }

    [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
    private static int _HexToInt( char h )
    {
      if( h >= '0' && h <= '9' )
      {
        return h - '0';
      }

      if( h >= 'a' && h <= 'f' )
      {
        return h - 'a' + 10;
      }

      if( h >= 'A' && h <= 'F' )
      {
        return h - 'A' + 10;
      }

      Assert.Fail( "Invalid hex character " + h );
      return -1;
    }

    public static void AddDependencyPropertyChangeListener( object component, DependencyProperty property, EventHandler listener )
    {
      if( component == null )
      {
        return;
      }
      Assert.IsNotNull( property );
      Assert.IsNotNull( listener );

      DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty( property, component.GetType() );
      dpd.AddValueChanged( component, listener );
    }

    public static void RemoveDependencyPropertyChangeListener( object component, DependencyProperty property, EventHandler listener )
    {
      if( component == null )
      {
        return;
      }
      Assert.IsNotNull( property );
      Assert.IsNotNull( listener );

      DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty( property, component.GetType() );
      dpd.RemoveValueChanged( component, listener );
    }

    #region Extension Methods

    public static bool IsThicknessNonNegative( Thickness thickness )
    {
      if( !IsDoubleFiniteAndNonNegative( thickness.Top ) )
      {
        return false;
      }

      if( !IsDoubleFiniteAndNonNegative( thickness.Left ) )
      {
        return false;
      }

      if( !IsDoubleFiniteAndNonNegative( thickness.Bottom ) )
      {
        return false;
      }

      if( !IsDoubleFiniteAndNonNegative( thickness.Right ) )
      {
        return false;
      }

      return true;
    }

    public static bool IsCornerRadiusValid( CornerRadius cornerRadius )
    {
      if( !IsDoubleFiniteAndNonNegative( cornerRadius.TopLeft ) )
      {
        return false;
      }

      if( !IsDoubleFiniteAndNonNegative( cornerRadius.TopRight ) )
      {
        return false;
      }

      if( !IsDoubleFiniteAndNonNegative( cornerRadius.BottomLeft ) )
      {
        return false;
      }

      if( !IsDoubleFiniteAndNonNegative( cornerRadius.BottomRight ) )
      {
        return false;
      }

      return true;
    }

    public static bool IsDoubleFiniteAndNonNegative( double d )
    {
      if( double.IsNaN( d ) || double.IsInfinity( d ) || d < 0 )
      {
        return false;
      }

      return true;
    }

    #endregion
  }
}
